﻿//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	XmlRulesSource.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Linq;

namespace Smart.Common.Rules
{
	/// <summary>
	/// Xml rules source
	/// </summary>
	public class XmlRulesSource : RulesSource
	{
		/// <summary>
		/// Xml "rules" element name
		/// </summary>
		public string RulesElementName { get; set; }

		/// <summary>
		/// Xml "rule" element name
		/// </summary>
		public string RuleElementName { get; set; }

		/// <summary>
		/// Xml "condition" attribute name
		/// </summary>
		public string ConditionAttributeName { get; set; }

		/// <summary>
		/// Xml "action" attribute name
		/// </summary>
		public string ActionAttributeName { get; set; }

		/// <summary>
		/// Xml rules source
		/// </summary>
		public XmlRulesSource(RulesCtx ctx)
			: base(ctx)
		{
			RulesElementName = Const.Rules.RulesName;
			RuleElementName = Const.Rules.RuleName;
			ConditionAttributeName = Const.Rules.ConditionName;
			ActionAttributeName = Const.Rules.ActionName;
		}

		/// <summary>
		/// Rules filter
		/// </summary>
		public override RulesFilter CreateFilter()
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Load instance by instance id
		/// </summary>
		/// <param name="instanceId">Instance id</param>
		/// <returns>Instance of TInstanceType</returns>
		public override IRulesInstance LoadInstance(string instanceId)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Load rules instance by rules filter
		/// </summary>
		/// <param name="filter">Filter to load rules instance</param>
		/// <returns>Rules instance</returns>
		public override IRulesInstance LoadInstance(RulesFilter filter)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Parse rules and load to context
		/// </summary>
		public override void ParseRulesToCtx(object rulesSource)
		{
			var rulesString = rulesSource as string;

			if (rulesString != null)
			{
				XDocument xDocument;

				using (var stream = new StringReader(rulesString))
					xDocument = XDocument.Load(stream, LoadOptions.None);

				var root = xDocument.Root;

				if (root == null)
					throw new ArgumentException("Rules source has no root node!");

				foreach (var element in root.Elements())
				{
					if (element.NodeType == XmlNodeType.Element && element.Name.LocalName.EqIC(RulesElementName))
					{
						rulesSource = element;
						break;
					}
				}
			}

			var xmlRules = rulesSource as XElement;

			if (xmlRules == null)
				throw new ArgumentOutOfRangeException("rulesSource");

			foreach (var element in xmlRules.Elements())
			{
				if (element.NodeType != XmlNodeType.Element || !element.Name.LocalName.EqIC(RuleElementName))
					continue;

				Ctx.Rules.Add(ParseRule(element));
			}
		}

		/// <summary>
		/// Parse rule
		/// </summary>
		public override IRule ParseRule(object rule)
		{
#warning Performance issue, use XDocument instead!
			var xml = rule as XElement;

			if (xml == null)
				throw new ArgumentOutOfRangeException("rule");

			string condition;
			string action;

			xml.SafeGetAttributes(ConditionAttributeName, out condition, ActionAttributeName, out action);

			if (condition == null || action == null)
				throw new ArgumentOutOfRangeException("rule");

			return CreateRule(ParseCondition(condition), ParseAction(action));
		}

		/// <summary>
		/// Parse rule condition
		/// </summary>
		public override IRuleCondition ParseCondition(object ruleCondition)
		{
			var condition = ruleCondition as string;
			if (condition != null)
			{
				var res = CreateCondition();

				var src = condition;

				var idx = ParsingExts.ParseBlock(src, 0, src.Length - 1, ParsingExts.OpenBracket, ParsingExts.CloseBracket, res, ConditionParserDetector, ConditionParser, ConditionParserAdder);

				if (idx < 0)
					throw new InvalidCastException("ruleCondition");
			}

			throw new NotSupportedException();
		}

		/// <summary>
		/// Parse rule action
		/// </summary>
		public override IRuleAction ParseAction(object ruleAction)
		{
			var action = ruleAction as string;
			if (action != null)
			{
				var res = CreateAction();

				var src = action;

				if (src.IsNullOrEmpty())
					throw new InvalidCastException("ruleAction");

				ParsingExts.ParseMethods(src, ParsingExts.SingleQuote, ParsingExts.Comma, ParsingExts.OpenBracket, ParsingExts.CloseBracket, res, ActionParserAdder);
			}

			throw new NotSupportedException();
		}

		/// <summary>
		/// Load instance variables to context
		/// </summary>
		public override void LoadInstanceVariablesToCtx()
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Creates rule
		/// </summary>
		protected virtual XmlRule CreateRule(IRuleCondition condition, IRuleAction action)
		{
			return new XmlRule(condition, action);
		}

		/// <summary>
		/// Creates condition
		/// </summary>
		protected virtual XmlRuleCondition CreateCondition()
		{
			return new XmlRuleCondition();
		}

		/// <summary>
		/// Creates condition function
		/// </summary>
		protected virtual XmlRuleConditionFunction CreateConditionFunction()
		{
			return new XmlRuleConditionFunction();
		}

		/// <summary>
		/// Creates action
		/// </summary>
		protected virtual XmlRuleAction CreateAction()
		{
			return new XmlRuleAction();
		}

		/// <summary>
		/// Creates action method
		/// </summary>
		protected virtual XmlRuleActionMethod CreateActionMethod()
		{
			return new XmlRuleActionMethod();
		}

		/// <summary>
		/// Detect block type
		/// </summary>
		/// <param name="src">string to search in</param>
		/// <param name="idx">Current position</param>
		/// <param name="startIdx">Current block start</param>
		/// <param name="endIdx">Current block end</param>
		/// <param name="block">Current block instance</param>
		/// <returns>-1 to stop; 1 - logic block; 2 - eq block; 3 - action block</returns>
		protected virtual int ConditionParserDetector(string src, int idx, int startIdx, int endIdx, XmlRuleCondition block)
		{
			if (block.IsSingle())
			{
				string blockContent;

				var closeIdx = ParsingExts.FindParenthesisEnd(src, idx, endIdx, ParsingExts.SingleQuote, ParsingExts.OpenBracket, ParsingExts.CloseBracket, out blockContent);

				if (closeIdx < 0)
					closeIdx = endIdx + 1;

				int opeidx;

				if (src[idx] == ParsingExts.SingleQuote)
				{
					string tmp;

					opeidx = ParsingExts.FindQuoteEnd(src, idx, endIdx, out tmp);

					if (opeidx < 0)
						return -1;

					opeidx++;
				}
				else
				{
					opeidx = src.SkipWhile(char.IsLetterOrDigit, idx, endIdx);

					if (opeidx < 0)
						return 3;
				}

				var opidx = src.SkipI(ParsingExts.WhiteSpaceWithNewLine, opeidx, endIdx);

				if (opidx < 0 || src[opidx] == ParsingExts.CloseBracket)
					return 3;

				var cidx = src.IndexOfAny(ParsingExts.ComparisonOperations, opeidx, closeIdx - opeidx);
				var qidx = src.IndexOf(ParsingExts.SingleQuote, opeidx, closeIdx - opeidx);

				return
					((cidx > 0) && ((qidx < 0) ^ (cidx < qidx)))
						? 2
						: 3;
			}

			var esIdx = src.SkipWhile(char.IsLetterOrDigit, idx, endIdx);

			if (esIdx < 0 || esIdx - idx <= 0)
				return -1;

			var op = src.Substring(idx, esIdx - idx);

			if (!op.IsLogicOperation())
				throw new InvalidCastException("op.IsLogicOperation");

			return 1;
		}

		/// <summary>
		/// Parse block type
		/// </summary>
		/// <param name="src">string to search in</param>
		/// <param name="idx">Current position</param>
		/// <param name="blockType">Detected block type</param>
		/// <param name="startIdx">Current block start</param>
		/// <param name="endIdx">Current block end</param>
		/// <param name="block">Current block instance</param>
		/// <returns>Index next after parsed blocked, -1 to stop, (if logic Operator return last index of block)</returns>
		protected virtual int ConditionParser(string src, int idx, int blockType, int startIdx, int endIdx, XmlRuleCondition block)
		{
			if (blockType == 1)
			{
				ComparisonOperator op;

				var endBlockIdx = ParsingExts.ParseLogicOperation(src, idx, endIdx, out op);

				if (block.Operator == ComparisonOperator.None)
					block.Operator = op;
				else if (block.Operator != op)
					throw new InvalidCastException("Block.Operator != Current.Operator");

				return endBlockIdx;
			}

			if (blockType == 2)
			{
				string operand1;
				string operand2;
				ComparisonOperator op;
				string parsedSrc;

				var endParanthesisIdx = ParsingExts.FindParenthesisEnd(src, idx, endIdx, ParsingExts.SingleQuote, ParsingExts.OpenBracket, ParsingExts.CloseBracket, out parsedSrc);

				if (endParanthesisIdx < 0)
					endParanthesisIdx = endIdx + 1;

				var endBlockIdx = ParsingExts.ParseComparisonBlock(src, idx, endParanthesisIdx - 1, out operand1, out op, out operand2);

				block.Left = operand1;
				block.Operator = op;
				block.Right = operand2;

				return endBlockIdx;
			}

			if (blockType == 3)
			{
				string fn;
				string[] fp;
				bool fcp;

				var fEndIdx = ParsingExts.FindMethodEnd(src, idx, endIdx, ParsingExts.SingleQuote, ParsingExts.Comma, ParsingExts.OpenBracket, ParsingExts.CloseBracket, out fn, out fp, out fcp);

				if (fEndIdx == -1)
					return -1;

				block.Operator = ComparisonOperator.Function;
				block.Function = CreateConditionFunction();
				block.Function.Name = fn;
				block.Function.Parameters = fp;
				block.Function.ContainsParenthesis = fcp;

				fEndIdx = src.SkipI(ParsingExts.WhiteSpaceWithNewLine, fEndIdx, endIdx);

				if (fEndIdx < 0)
					fEndIdx = endIdx + 1;

				return fEndIdx;
			}

			return -1;
		}


		/// <summary>
		/// Add new child to condition
		/// </summary>
		/// <param name="condition">Current condition</param>
		/// <returns>Child condition</returns>
		protected virtual XmlRuleCondition ConditionParserAdder(XmlRuleCondition condition)
		{
			if (condition.Operator != ComparisonOperator.None
				&& condition.Operator != ComparisonOperator.Or
				&& condition.Operator != ComparisonOperator.And)
				throw new InvalidCastException("Condition.Operator");

			var childCondition = CreateCondition();

			if (condition.Children == null)
				condition.Children = new List<IRuleCondition>();

			condition.Children.Add(childCondition);

			return childCondition;
		}

		/// <summary>
		/// Adds function to action list
		/// </summary>
		protected virtual void ActionParserAdder(XmlRuleAction action, string name, string[] parameters, bool containsParenthesis)
		{
			if (action.Methods == null)
				action.Methods = new List<XmlRuleActionMethod>();

			var method = CreateActionMethod();

			method.Name = name;
			method.Parameters = parameters;
			method.ContainsParenthesis = containsParenthesis;

			action.Methods.Add(method);
		}
	}
}